using SQLRunnerLib.Services;
using SQLServices.Repository;
using SQLServices.Runners;
using SQLServices.Runners.Events;
using SQLServices.TransMgr;

namespace SQLRunnerLib.TransMgr
{
    /// <summary>
    /// The transaction connection manager executes the set of SQL scripts
    /// within a transaction. If an error or anomoly is detected the
    /// execution is cancelled and the transaction is rollback.
    /// This manager opens two connections to the database, the first
    /// one is to execute the scripts, the second one is to monitor for
    /// anomolies like a rollback in the main connection. 
    /// The manager uses the binding feature in SQL server to monitor the
    /// main connection.
    /// </summary>
    class CommitTransactionConnMgr:AbstractManager
    {
        protected ISQLDBConn _monitorConn;

        private delegate void CancelExecutionHandler();

        private readonly CancelExecutionHandler _cancelRunner;

        public CommitTransactionConnMgr(IRunner runner) : base(runner)
        {
            _cancelRunner = runner.CancelExecution;
        }

        /// <summary>
        /// When the Runner has finished the execution of all scripts it calls this method.
        /// Each Transaction manager must implement this method.
        /// </summary>
        protected override void OnExecutionFinished(object sender, ExecutionFinishedEventArgs e)
        {
            if (_isInTransaction)
            {
                while(_mainConn.GetTranCount() != 0)
                    _mainConn.Execute("Commit");
                LogMsg("Transaction was commited");
                CleanConnection(ref _mainConn);
                CleanConnection(ref _monitorConn);
                _isInTransaction = false;
            }
        }

        /// <summary>
        /// Called when an exception is returned when a script is executed. Each Transaction 
        /// manager must implement this method
        /// </summary>
        protected override void OnScriptError(object sender, ScriptExecutionErrorEventArgs e)
        {
            if (!_isInTransaction) return;
            LogMsg("Exception running the script - rolling back the transaction");
            CleanConnection(ref _monitorConn); // this probably rollbacks the connection
            CleanConnection(ref _mainConn);
            _isInTransaction = false;
            _cancelRunner();
            //throw new ConnMgrException("The transaction was rollback");
        }

        /// <summary>
        /// Called after a script has been executed. Each transaction manager must implement this
        /// method
        /// 
        /// </summary>
        protected override void OnScriptExecuted(object sender, ScriptExecutedEventArgs e)
        {
            return;
        }

        /// <summary>
        /// Hook into the execution process that runs just after the execution of the script
        /// Check that @@trancount is 1 in the monitor trans, other wise we throw an exception
        /// </summary>
        protected override void DoAfterExecution()
        {
            if (_monitorConn.GetTranCount() != 1)
                throw new ConnMgrException("The main transaction has been commited or rollback, cancelling execution");
        }

        /// <summary>
        /// Hook into the execution process that runs before the execution takes place
        /// Open main and monitor connections
        /// Begin a transaction in both connections
        /// Get token from monitor connection and bind the main connection to the monitor one
        /// </summary>
        protected override void DoBeforeExecution()
        {
            if (_isInTransaction) return;
            _mainConn = ServiceContainer.SQLConnection.GetDatabase().Clone();
            _monitorConn = ServiceContainer.SQLConnection.GetDatabase().Clone();
            _monitorConn.Execute("BEGIN TRAN");
            _mainConn.Execute("BEGIN TRAN");
            _mainConn.SetToken(_monitorConn.GetToken());
            _isInTransaction = true;
            LogMsg("Transaction was started");
        }
    }
}
